/**
 * 何为受控组件？默认判断条件为： value === undefined
 * 一个合格的表单组件：
 *   1. 组件内容变更时能改变 Form.Store
 *   1. Form 能通过 setFiledsValue 控制该组件
 *   1. Form 能通过 resetFieldsValue 初始化组件值
 * 作为非受控组件时：
 *   1. 能在内容变更时正确触发 onChange
 *   1. 能在未传递 value 时使用 defaultValue 作为初始值
 */
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { isEqual, uniqueId } from 'lodash';

export function isComponentControlled<T>(
  props: Pick<FormInputProps<T>, 'value'>
) {
  return props.value !== undefined;
}

export type Nullable<T> = T | null;
export type FormInputValueType<T> =
  | (T extends React.ReactText ? T : Nullable<T>)
  | undefined;
export type FormInputOnChangeType<T> = (
  newValue: FormInputValueType<T>
) => void | undefined;
export type FormInputProps<T> = {
  /**
   * 表单元素的 value，onChange
   */
  value?: FormInputValueType<T>;
  onChange?: FormInputOnChangeType<T>;
};
export type FormInputOptions<T> = {
  /**
   * 用于比较 props.value 是否变更，默认值为 lodash.isEqual
   */
  isEqual?: (
    value1?: FormInputValueType<T>,
    value2?: FormInputValueType<T>
  ) => boolean;
  /**
   * 在组件由受控组件转为非受控组件时
   * true: 保留上一步的 value 值
   * false（默认值）: 保持与 props.value 同步
   */
  preserve?: boolean;
  /**
   * 用于判断当前组件是否为受控组件
   */
  isComponentControlled?: (props: Pick<FormInputProps<T>, 'value'>) => boolean;
  /**
   * 非受控组件的默认值
   */
  defaultValue?: FormInputValueType<T>;
};

export type MFormInputProps<T> = FormInputProps<T> & FormInputOptions<T>;

/**
 * 快速创建表单元素
 *   1. 表单元素的 value === undefined 时表示该组件为非受控组件
 *   1. 表单元素的 value !== undefined 时表示该组件为受控组件
 *   1. 采用 lodash.isEqual 来比较表单值是否需要更新
 *   1. onChang 必须满足以下条件：
 *     - 若组件为受控组件，则调用之后的下一帧，value 的值 === 传入的值
 */
export default function useFormInput<T>(
  props: FormInputProps<T>,
  options: FormInputOptions<T> = useFormInput.defaultOptions
) {
  const [name] = useState(() => uniqueId('form-input-'));
  const { value, onChange } = props;
  const {
    isEqual,
    preserve,
    isComponentControlled,
    defaultValue,
  } = Object.assign({}, useFormInput.defaultOptions, options);
  const isControlled = useMemo(() => {
    return isComponentControlled({
      value,
    });
  }, [isComponentControlled, value]);
  /**
   * Q: 为什么不用 useState 储存组件状态？
   * A: 之前采取的策略是：不区分受控和非受控组件，通过同步受控组件和非受控组件的值来达到一致地体验。
   *    这种策略会导致 1、2 次多余的渲染，因此采用 useRef 进行渲染控制。
   *    但是想了一下，只要使用 useState 时切换处理受控和非受控组件的数据源，就不会有上面的问题。
   */
  const [, setRefreshCounter] = useState(0);
  const selfValueRef = useRef(isControlled ? value : defaultValue);
  const setSelfValue = useCallback<FormInputOnChangeType<T>>(
    (newValue) => {
      if (isControlled && onChange == null) {
        // 未提供 onChange 方法的受控组件将不响应更新
        return;
      }
      const isModified = !isEqual(selfValueRef.current, newValue);
      if (!isModified) {
        return;
      }
      selfValueRef.current = newValue;
      onChange && onChange(newValue);
      setRefreshCounter((v) => v + 1);
    },
    [isControlled, isEqual, onChange]
  );
  const prevValueRef = useRef<FormInputValueType<T>>(value);
  const requireEmitOnChangeRef = useRef(false);
  const prevIsControlled = isComponentControlled({
    value: prevValueRef.current,
  });
  if (prevIsControlled !== isControlled && !isControlled) {
    // 组件由受控组件转为非受控组件
    if (!preserve) {
      selfValueRef.current = defaultValue;
      requireEmitOnChangeRef.current = true;
    }
  }
  if (prevIsControlled !== isControlled && isControlled) {
    // 组件由非受控组件转为受控组件
  }
  if (isControlled && !isEqual(selfValueRef.current, value)) {
    selfValueRef.current = value;
    requireEmitOnChangeRef.current = false;
  }
  prevValueRef.current = value;
  useEffect(() => {
    if (!requireEmitOnChangeRef.current) {
      return;
    }
    requireEmitOnChangeRef.current = false;
    if (onChange != null) {
      onChange(selfValueRef.current);
      setRefreshCounter((v) => v + 1);
    }
  }, [onChange]);

  return [
    selfValueRef.current,
    setSelfValue,
    {
      name,
      isControlled,
    },
  ] as const;
}
const defaultIsEqual = isEqual;
useFormInput.defaultOptions = {
  isEqual: defaultIsEqual,
  prevserve: false,
  isComponentControlled,
};

type ConfirmedFormInputOptions<T> = FormInputOptions<T> & {
  preserveChange?: boolean;
};

export type MConfirmedFormInputProps<T> = FormInputProps<T> &
  ConfirmedFormInputOptions<T>;

export function useConfirmedFormInput<T>(
  props: FormInputProps<T>,
  options: ConfirmedFormInputOptions<T> = useConfirmedFormInput.defaultOptions
) {
  const { value, onChange } = props;
  const { isEqual, preserveChange } = Object.assign(
    {},
    useConfirmedFormInput.defaultOptions,
    options
  );
  /**
   * 缓存当前输入
   * Q: 为什么用 useRef 而不是用 useState
   * A: 已经使用 useFormInput 来进行组件状态管理，若再次使用 useState，
   *    会导致组件多进行一次渲染。
   */
  const [, setRefreshCounter] = useState(0);
  const holdingValueRef = useRef(value);
  const setHoldingValue = useCallback<FormInputOnChangeType<T>>((newValue) => {
    holdingValueRef.current = newValue;
  }, []);
  const prevRef = useRef(value);
  const handlePropsValueChange = useCallback(
    (newValue: FormInputValueType<T>) => {
      const isModified = !isEqual(prevRef.current, holdingValueRef.current);
      if (!isModified) {
        /**
         * 未进行任何更改，跟随变化
         */
        setHoldingValue(newValue);
        return;
      }
      if (preserveChange) {
        return;
      }
      setHoldingValue(newValue);
    },
    [isEqual, preserveChange, setHoldingValue]
  );
  if (!isEqual(prevRef.current, value)) {
    handlePropsValueChange(value);
  }
  prevRef.current = value;

  /**
   * 控制函数
   */
  const holdingValue = holdingValueRef.current;
  const isControlled = isComponentControlled({ value });
  const [selfValue, setSelfValue, { name }] = useFormInput(
    {
      ...props,
      value: holdingValue,
      onChange: setHoldingValue,
    },
    options
  );
  const isModified = !isEqual(value, selfValue);

  const handleConfirm = useCallback(() => {
    if (isControlled) {
      prevRef.current = selfValue;
      holdingValueRef.current = selfValue;
      if (isModified) {
        setRefreshCounter((v) => v + 1);
      }
    }
    onChange && onChange(selfValue);
  }, [isControlled, isModified, selfValue, onChange]);
  const handleReset = useCallback(() => {
    setHoldingValue(value);
    setRefreshCounter((v) => v + 1);
  }, [setHoldingValue, value]);
  return [
    selfValue,
    setSelfValue,
    handleConfirm,
    handleReset,
    { name, isControlled, isModified },
  ] as const;
}

useConfirmedFormInput.defaultOptions = {
  isEqual: defaultIsEqual,
  prevserve: false,
  preserveChange: false,
  isComponentControlled,
};
